/** Stream accessors

 Written in the D programming language 1.0

Authors:	Wei Li <oldrev@gmail.com>
Copyright:	Copyright (c) 2007 by Wei Li 
License:	BSD
*/

module dotmars.io.accessor;

import dotmars.base.typetraits;
import dotmars.base.stdtypes;
import dotmars.base.convert;
import dotmars.base.stdexcept;
import dotmars.io.stream;
import dotmars.io.bufferedstream;
import dotmars.io.memorystream;

import dotmars.text.encoding;
import dotmars.text.utf8;

// StreamWriter, StreamReader, TextReader, TextWriter, StringReader, StringWriter


//I think the BinaryWriter & BinaryReader is not too useful

//we used the little endian format

/*
class BinaryWriter : IDisposable
{
	protected Stream m_stream = null;
	protected Encoding m_encoding = null;

	public this(Stream output)
	in {
		assert(output !is null);
	}
	body {
		this(output, new UTF8Encoding(false));
	}

	public this(Stream output, Encoding encoding)
	in {
		assert(output !is null);
		assert(encoding !is null);
	}
	body {
		m_stream = output;
		m_encoding = encoding;
	}

	public ~this() {
		close();
	}

	public override void close() {
		flush();
		m_stream.close();
	}

	public Stream baseStream() {
		return m_stream;
	}

	public void flush() {
		m_stream.flush();
	}

	public long seek(long offset, SeekOrigin origin)
	in {
		assert(m_stream.seekable);
	}
	out(r) {
		assert(r >= 0);
	} 
	body {
		return m_stream.seek(offset, origin);
	}

	public void write(T)(T value) 
	{
		static if(IsIntegerType!(T)) {
			writeInteger(value);
		}
		static if(is(T == char)) {
			writeChar(value);
		}
		static if(is(T == ubyte[])) {
			m_stream.write(value);
		}
		else static if(is(T == string)) {
			writeString(value);
		}
		else static if(IsStaticArrayType!(T) && is(typeof(T[0]) == char)) {
			writeString(value);
		}
		else { //Float, Imaginary and Complex
			m_stream.write(&value, T.sizeof);
		}
	}

	protected void writeInteger(T)(T value)
	{
		static assert(IsIntegerType!(T) || IsCharType!(T));
		version(BigEndian) {
			static if(T.sizeof > 1)
				value = BitConverter.toLittleEndian(value);
		}
		m_stream.write(&value, T.sizeof);
	}

	protected void writeChar(char value)
	{
		char[1] str = [ value ];
		writeString(str);
	}

	protected void writeString(string str) {
		Sink!(ubyte) sink = (ubyte u) { write(u); };
		m_encoding.encode(str, sink);
	}
}


///////////////////////////////////////////////////////////////////////////////

//Not like .Net FX, there is no buffer in this implemention
class BinaryReader : IDisposable
{
	private Stream m_stream;
	private Encoding m_encoding;

	public this(Stream stream)
	{
		this(stream, new UTF8Encoding(false));
	}

	public this(Stream stream, Encoding encoding)
	in {
		assert(stream !is null);
		assert(encoding !is null);
	}
	body {
		m_stream = stream;
		m_encoding = encoding;
	}

	public override void close()
	{
		m_stream.close();
	}

	protected void readRaw(void* data, size_t count)
	in {
		assert(data !is null);
	}
	body {
		size_t n = m_stream.read(data, count);
		if(n != count)
			throw new IOException("Unable to read data from stream");
	}

	protected T readInteger(T)()
	{
		static assert(IsIntegerType!(T));
		T value;
		readRaw(&value, T.sizeof);

		version(BigEndian) {
			static if(T.sizeof > 1)
				value = BitConverter.toBigEndian(value);
		}
		return value;
	}

	public size_t read(ubyte[] bytes) 
	in {
		assert(bytes !is null);
	}
	body {
		return m_stream.read(bytes);
	}

	public byte readByte() {
		return readInteger!(byte)();
	}

	public ubyte readUbyte() {
		return readInteger!(ubyte)();
	}

	public short readShort() {
		return readInteger!(short)();
	}

	public ushort readUshort() {
		return readInteger!(ushort)();
	}

	public int readInt() {
		return readInteger!(int)();
	}

	public uint readUint() {
		return readInteger!(uint)();
	}

	public long readLong() {
		return readInteger!(long)();
	}

	public ulong readUlong() {
		return readInteger!(ulong)();
	}

	public float readFloat() {
		float value;
		readRaw(&value, value.sizeof);
		return value;
	}

	public double readDouble() {
		double value;
		readRaw(&value, value.sizeof);
		return value;
	}

	public real readReal() {
		real value;
		readRaw(&value, value.sizeof);
		return value;
	}

	public ifloat readIfloat() {
		ifloat value;
		readRaw(&value, value.sizeof);
		return value;
	}

	public idouble readIdouble() {
		idouble value;
		readRaw(&value, value.sizeof);
		return value;
	}

	public ireal readIreal() {
		ireal value;
		readRaw(&value, value.sizeof);
		return value;
	}

	public cfloat readCfloat() {
		cfloat value;
		readRaw(&value, value.sizeof);
		return value;
	}

	public cdouble readCdouble() {
		cdouble value;
		readRaw(&value, value.sizeof);
		return value;
	}

	public creal readCreal() {
		creal value;
		readRaw(&value, value.sizeof);
		return value;
	}

	public ubyte[] readBytes(size_t count) {
		ubyte[] bytes = new ubyte[count];
		readRaw(bytes.ptr, bytes.length);
		return bytes;
	}

	public char readChar() {
		char c; //ASCII character
		readRaw(&c, c.sizeof);
		if(c >= 0x80)
			throw new IOException("Illegal ASCII character");
		return c;
	}

	public string readChars() {
		assert(false);
		return null;
	}

	public string readString() {
		assert(false);
		return null;
	}
}
*/

////////////////////////////////////////////////////////////////////////////////

abstract class TextWriter : IDisposable
{
	version(Windows)
		public const char[2] DefaultNewLine = "\n\r";

	version(Posix)
		public const char[1] DefaultNewLine = "\n";

	private string m_newLine;


	public this()
	{
		m_newLine = DefaultNewLine;
	}

	protected abstract void writeString(string value);

	public abstract Encoding encoding();

	public abstract Stream stream();
	
	public override void close(){
		//do nothing
	}

	public void flush() {
		//do nothing
	}

	public string newLine() {
		return m_newLine;
	}

	public void newLine(string nl) {
		m_newLine = nl;
	}

	public void print(string fmt, ...) {
		print(_argptr, _arguments, fmt);
	}

	public void print(Argument argptr, TypeInfo[] argTypes, string fmt) {
		Formatter.format(&write!(char),argptr, argTypes, fmt);
	}

	public void printLine(string fmt, ...) {
		print(_argptr, _arguments, fmt);
		writeString(newLine());
	}

	public void write(T)(T value)
	{
		static if(IsIntegerType!(T)) {
			Integer.getChars!(T)(value, &write!(char));		
		}
		else static if(IsFloatType!(T)) {
			Float.getChars!(T)(value, &write!(char));
		}
		else static if(IsImaginaryType!(T)) {
			Float.getChars!(T)(cast(real)value, &write!(char));
		}
		else static if(IsComplexNumberType!(T)) {
			writeString(Convert.toString(value));
		}
		else static if(is(T == string)) {
			writeString(value);
		}
		else static if(IsStaticArrayType!(T) && is(typeof(T[0]) == char)) {
			writeString(value);
		}
		else static if(is(T == char)) {
			writeChar(value);
		}
		else static if(is(T == struct) || is(T == union) || 
				is(T == interface) || is(T == class)) {
			writeString(value.toString());
		}
		else {
			assert(false, "TextWriter.write(T): Unknown Type");
		}
	}

	protected void writeChar(char value)
	{
		if(value > 0x80 )
			throw new ArgumentException("TextWriter.write(char value): ASCII character only");
		char[1] str = [ value ];
		writeString(str);
	}

	public void writeLine(T)(T value) {
		write(value);
		writeString(newLine);
	}
}

////////////////////////////////////////////////////////////////////////////////

abstract class TextReader : IDisposable
{
	protected this()
	{
		close();
	}

	public abstract Stream	 stream();
	public abstract Encoding encoding();
	public abstract int peek();
	public abstract int read();

	public size_t read(char[] strbuf)
	in 
	{
		assert(strbuf !is null);
	}
	body
	{
		size_t n = 0;
		do {
			int c = read();
			if (c == -1)
				return 0;
			strbuf[n++] = cast(char)c;
		} while (n < strbuf.length);
		return n;
	}

	public abstract string readLine();

	public string readToEnd()
	{
		char[1024] buffer;
		int n;
		string ret;

		while ((n = read(buffer[0..$])) != 0) {
			ret ~= buffer[0 .. n];
		}

		return ret;
	}

	public abstract override void close();
}

////////////////////////////////////////////////////////////////////////////////

class StreamWriter : TextWriter
{
	public alias TextWriter.write write;
	
	public const size_t DefaultBufferSize = 1024;

	protected ubyte[] m_buffer;
	protected size_t m_bufLen;
	protected Stream m_stream;
	protected Encoding m_encoding;
	protected bool	m_autoFlush;


	public this(Stream stream, Encoding encoding, size_t bufferSize)
	in {
		assert(stream !is null);
	}
	body {
		m_autoFlush = false;
		m_stream = stream;
		m_buffer = new ubyte[bufferSize];
		m_bufLen = 0;
		m_encoding = encoding;
	}

	public this(Stream stream) {
		this(stream, new UTF8Encoding, DefaultBufferSize);
	}

	public this(Stream stream, Encoding encoding) {
		this(stream, encoding, DefaultBufferSize);
	}

	public bool autoFlush() {
		return m_autoFlush;
	}

	public void autoFlush(bool af) {
		m_autoFlush = af;
	}

	public override void flush()
	{
		if(m_bufLen > 0) {
			m_stream.write(m_buffer[0..m_bufLen]);
			m_bufLen = 0;
		}
		m_stream.flush();
	}

	public override void close() {
		flush();
		m_stream.close();
	}


	public override Encoding encoding() {
		return m_encoding;
	}

	public override Stream stream() {
		return m_stream;
	}

	protected override void writeString(string str)
	{
		if(m_encoding !is null) {
			m_encoding.encode(str, &putByte);
		}
		else {
			foreach(char c; str) {
				putByte(cast(ubyte) c);
			}
		}

		if(autoFlush)
			flush();
	}

	protected void putByte(ubyte b)
	{
		if(m_bufLen >= m_buffer.length) {
			m_stream.write(m_buffer[0..m_bufLen]);
			m_bufLen = 0;
		}
		m_buffer[m_bufLen++] = b;
	}
}

//TODO: StreamReader
////////////////////////////////////////////////////////////////////////////////
/*
class StreamReader : IDisposable
{
	public const size_t DefaultBufferSize = 1024;

	protected BufferedStream m_bstream;
	protected Encoding m_encoding;
	protected Stream m_stream;
	

	public this(Stream source, Encoding enc, size_t bufsize)
	in {
		assert(source !is null);
		assert(enc !is null);
		assert(bufsize > 0);
	}
	body {
		m_encoding = enc;
		m_stream = source;
		m_bstream = new BufferedStream(source, bufsize);
	}

	public this(Stream source)
	{
		this(source, new UTF8Encoding, DefaultBufferSize);
	}

	public this(Stream source, Encoding enc)
	{
		this(source, enc, DefaultBufferSize);
	}

	public override Encoding encoding() {
		return m_encoding;
	}

	public override Stream stream() {
		return m_stream;
	}

	public override int peek()
	{
	}

	public override size_t read(char[] strbuf)
	{
	}

	public override string readLine()
	{
	}

	public override string readToEnd()
	{
	}

	public override void close()
	{
		m_stream.close();
	}
}

*/
